//
//  GLShader.m
//  LMPhotoVideoSDK
//
//  Created by Aimar on 16/7/6.
//  Copyright © 2016年 魁钺会. All rights reserved.
//

#import "GLShader.h"

static GLuint                   sCurrentProgram = 0;
static NSMutableDictionary*     sPresetShaders;

@interface GLShader () {
    GLchar*        _vertexString;
    GLchar*        _fragmentString;
    
    GLuint      _projectionHandle;
    GLuint      _modelViewHandle;
    GLuint      _positionHandle;
}

@end

@implementation GLShader

- (id)initWithVertexString:(const GLchar*)vString
            fragmentString:(const GLchar*)fString {
    self = [super init];
    if (self) {
        _vertexString = (GLchar*)vString;
        NSLog(@"vertex shader:\n%s", _vertexString);
        _fragmentString = (GLchar*)fString;
        NSLog(@"fragment shader:\n%s", _fragmentString);
    }
    
    return self;
}

- (id)initWithVertexFile:(NSString*)vFile
            fragmentFile:(NSString*)fFile {
    GLchar* vertex = (GLchar *)[[NSString stringWithContentsOfFile:vFile encoding:NSUTF8StringEncoding error:nil] UTF8String];
    GLchar* fragment = (GLchar *)[[NSString stringWithContentsOfFile:fFile encoding:NSUTF8StringEncoding error:nil] UTF8String];
    return [self initWithVertexString:vertex fragmentString:fragment];
}

- (void)releaseShader {
    glDeleteShader(self.programId);
    _programId = 0;
}

- (BOOL)bind {
    if (_programId == 0) {
        if (![self initShader]) {
            return NO;
        }
    }
    
    if (sCurrentProgram != _programId) {
        sCurrentProgram = _programId;
        glUseProgram(_programId);
    }
    
    [self onProgramBind];
    
    return YES;
}

- (BOOL)onProgramCreate {
    _projectionHandle = glGetUniformLocation(_programId, "projection");
    _modelViewHandle = glGetUniformLocation(_programId, "model");
    
    _positionHandle = glGetAttribLocation(_programId, "position");
    return YES;
}

- (void)onProgramBind {
    glEnableVertexAttribArray(_positionHandle);
}

- (void)setProjectionMatrix:(mat4)projection {
    glUniformMatrix4fv(_projectionHandle, 1, GL_FALSE, projection.Pointer());
}

- (void)setModelViewMatrix:(mat4)model {
    glUniformMatrix4fv(_modelViewHandle, 1, GL_FALSE, model.Pointer());
}

- (void)setPosition:(GLfloat *)position component:(GLint)component {
    glVertexAttribPointer(_positionHandle, component, GL_FLOAT, GL_FALSE, component * sizeof(GLfloat), position);
}

#pragma mark Internal InitShader
- (BOOL)initShader {
    _programId = glCreateProgram();
    if (_programId == 0) {
        return NO;
    }
    
    GLuint vertShader, fragShader;
    if (![self compileShader:&vertShader type:GL_VERTEX_SHADER shaderString:_vertexString]) {
        NSLog(@"Failed to compile vertex shader");
        return NO;
    }
    
    if(![self compileShader:&fragShader type:GL_FRAGMENT_SHADER shaderString:_fragmentString]) {
        NSLog(@"Failed to compile fragment shader");
        return NO;
    }
    
    // Attach vertex shader to program.
    glAttachShader(_programId, vertShader);
    
    // Attach fragment shader to program.
    glAttachShader(_programId, fragShader);
    
    // Link program
    if (![self linkProgram:_programId]) {
        NSLog(@"Failed to link program: %d", _programId);
        
        if (vertShader) {
            glDeleteShader(vertShader);
            vertShader = 0;
        }
        
        if (fragShader) {
            glDeleteShader(fragShader);
            fragShader = 0;
        }
        
        if (_programId) {
            glDeleteProgram(_programId);
            _programId = 0;
        }
        
        return NO;
    }
    
    return [self onProgramCreate];
}

#pragma mark Internal CompliShader
- (BOOL)compileShader:(GLuint *)shader type:(GLenum)type shaderString:(GLchar *)string {
    GLint status;
    const GLchar *source = string;
    if (!source) {
        NSLog(@"Failed to load vertex shader");
        return NO;
    }
    
    *shader = glCreateShader(type);
    glShaderSource(*shader, 1, &source, NULL);
    glCompileShader(*shader);
    
#if defined(DEBUG)
    GLint logLength;
    glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 0) {
        GLchar *log = (GLchar *)malloc(logLength);
        glGetShaderInfoLog(*shader, logLength, &logLength, log);
        NSLog(@"Shader compile log:\n%s", log);
        free(log);
    }
#endif
    
    glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
    if (status == 0) {
        glDeleteShader(*shader);
        return NO;
    }
    
    return YES;
}

#pragma mark Internal LinkShader
- (BOOL)linkProgram:(GLuint)program {
    GLint status;
    glLinkProgram(program);
    
#if defined(DEBUG)
    GLint logLength;
    glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 0) {
        GLchar *log = (GLchar *)malloc(logLength);
        glGetProgramInfoLog(program, logLength, &logLength, log);
        NSLog(@"Program link log:\n%s", log);
        free(log);
    }
#endif
    
    glGetProgramiv(program, GL_LINK_STATUS, &status);
    if (status == 0) {
        return NO;
    }
    
    return YES;
}
@end
